Import mojo
Import agfx

Const PROP_MAP_ORTOGONAL:Int = 0
Const PROP_MAP_ISOMETRIC:Int = 1


Const FLAG_SWAP_X:Int = $8000
Const FLAG_SWAP_Y:Int = $4000

'Global FlippedHorizontallyFlag:Int = 2147483648
'Global FlippedVerticallyFlag:Int   = 1073741824
'Global GID_Mask:Int = 16777215

Class TTiled_Map
	
	Field version:			String		= "1.0"
	Field orientation:		Int			= PROP_MAP_ORTOGONAL
	Field width:			Int 		= 20
	Field height:			Int 		= 20
	Field tilewidth:		Int 		= 32
	Field tileheight:		Int 		= 32
	Field filename:			String		= ""
	Field properties:		TProperties	
	Field tilesets:			ArrayList<TTiled_Tileset>
	Field layers:			ArrayList<TTiled_Layer>
	Field object_groups:	ArrayList<TTiled_ObjectGroup>

	Field parser:			XMLParser 	= New XMLParser
	Field xml_map:			XMLDocument	

	Field position:			TVector2 	= New TVector2(0,0)
	
	'------------------------------------------------------------------------------------
	' NEW
	'------------------------------------------------------------------------------------

	Method New()
		Self.properties		= New TProperties()	
		Self.tilesets		= New ArrayList<TTiled_Tileset>	
		Self.layers			= New ArrayList<TTiled_Layer>	
		Self.object_groups	= New ArrayList<TTiled_ObjectGroup>	
	End Method
	

	'------------------------------------------------------------------------------------
	' LOAD
	'------------------------------------------------------------------------------------

	Method Load:Bool(_filename:String)

		Self.filename = _filename
		Self.xml_map = Self.parser.ParseString(LoadString("maps/"+Self.filename))

		If DEBUG Then Print("<MAP>")
		
		' <MAP>'
		' prepare MAP properties'

		Self.version = xml_map.Root.GetAttribute("version")

		If (xml_map.Root.GetAttribute("orientation")="orthogonal") Then
			Self.orientation =  PROP_MAP_ORTOGONAL
		End

		If (xml_map.Root.GetAttribute("orientation")="isometric") Then
			Self.orientation =  PROP_MAP_ISOMETRIC
		End

		Self.width  = Int(xml_map.Root.GetAttribute("width"))
		Self.height = Int(xml_map.Root.GetAttribute("height"))
		Self.tilewidth  = Int(xml_map.Root.GetAttribute("tilewidth"))
		Self.tileheight = Int(xml_map.Root.GetAttribute("tileheight"))

		If DEBUG Then 
			Print("version      : "+Self.version)
			Print("orientation  : "+Self.orientation)
			Print("width        : "+Self.width)
			Print("height       : "+Self.height)
			Print("tilewidth    : "+Self.tilewidth)
			Print("tileheight   : "+Self.tileheight)
			
		EndIf
		
		Local node:XMLElement

		'Decode tiled map file
		For node = EachIn xml_map.Root.Children
			
			Select node.Name()

				Case "properties"
					If DEBUG Then Print(" <PROPERTIES>")
					Self.properties.Add(node,"property","name","value")

				Case "tileset"					
					
					Local name:String 		= node.GetAttribute("name")
					If DEBUG Then Print("  name: "+name)
										
					Local source:String		= node.GetAttribute("source")
					
					If source.Length()>0 Then
					
						source = source.Replace(".tsx",".xml")
						If DEBUG Then Print("  source: "+source)
						Local parser:XMLParser 	= New XMLParser						
						Local xml_tileset:XMLDocument = parser.ParseString(LoadString("maps/"+source))
						Local node_source:XMLElement = xml_tileset.Root
						
						Local ts:TTiled_Tileset = New TTiled_Tileset(node,node_source)
						Self.tilesets.Add(ts)
						
					Else
						
						Local ts:TTiled_Tileset = New TTiled_Tileset(node)
						Self.tilesets.Add(ts)
					End If
				
					
					
				Case "layer"
				
					Local tl:TTiled_Layer = New TTiled_Layer(node)
					Self.layers.Add(tl)
					
				Case "objectgroup"
				
					Local og:TTiled_ObjectGroup = New TTiled_ObjectGroup(node)
					Self.object_groups.Add(og)

			End Select

		Next

		' Prepare data
		
		Self.Prepare()

	End Method

	'------------------------------------------------------------------------------------
	' Get tileset array by tileset name
	'------------------------------------------------------------------------------------
	Method GetTilesetsByName:TTiled_Tileset(name:String)
		Local tileset:TTiled_Tileset
		
		For tileset = EachIn Self.tilesets
			
			If tileset.name = name Then Return tileset
		Next
		Error("TILESET "+name+" doesn't EXIST !")
	End Method
	'------------------------------------------------------------------------------------
	' Get tileset ID by tile GID
	'------------------------------------------------------------------------------------

	Method GetTilesetIDByGID:Int(gid:Int)

		Local tid:Int = -1

		For Local i:Int = Self.tilesets.Size()-1 to 0 step -1
			tid = tilesets.Get(i).firstgid
			if gid >= tid Then Return i
		Next 

		Return -1

	End Method

	'------------------------------------------------------------------------------------
	' Get ID from Object Group by name
	'------------------------------------------------------------------------------------
	
	Method GetObjectGroupIDbyName:Int(n:String)
		Local res:Int = -1
		Local obj:TTiled_ObjectGroup

		For obj = EachIn Self.object_groups
			If obj.name.ToUpper()=n.ToUpper() Then 
				res = Self.object_groups.IndexOf(obj)
			EndIf
		Next
		Return res
	End Method

	'------------------------------------------------------------------------------------
	' Get object from Object Group ID by name
	'------------------------------------------------------------------------------------
	
	Method GetObjectGroupByName:TTiled_ObjectGroup(n:String)
		Local res:Int = -1
		Local obj:TTiled_ObjectGroup
		For obj = EachIn Self.object_groups
			If obj.name.ToUpper()=n.ToUpper() Then 
				Return obj
			EndIf
		Next
		Return null
	End Method

	'------------------------------------------------------------------------------------
	' Get ID from layers by name
	'------------------------------------------------------------------------------------
	
	Method GetLayerIDbyName:Int(n:String)
		Local res:Int = -1
		Local layer:TTiled_Layer
		For layer = EachIn Self.layers
			If layer.name.ToUpper()=n.ToUpper() Then 
				res = layers.IndexOf(layer)
			EndIf
		Next
		Return res
	End Method

	'------------------------------------------------------------------------------------
	' Get layer from layers by name
	'------------------------------------------------------------------------------------
	
	Method GetLayerByName:TTiled_Layer(n:String)
		Local res:Int = -1
		Local layer:TTiled_Layer
		For layer = EachIn Self.layers
			If layer.name.ToUpper()=n.ToUpper() Then 
				Return layer
			EndIf
		Next
		Return null
	End Method
	
	'------------------------------------------------------------------------------------
	' Set position
	'------------------------------------------------------------------------------------
	Method SetPosition:Void(x:Int,y:Int)
		Self.position.Set(x,y)
	End Method
	
	'------------------------------------------------------------------------------------
	' Set position
	'------------------------------------------------------------------------------------
	Method SetPosition:Void(pos:TVector2)
		Self.position.Set(pos.x,pos.y)
	End Method
	
	'------------------------------------------------------------------------------------
	' Prepare DATA
	'------------------------------------------------------------------------------------
	Method Prepare:Void()

		' tilesets
		Local tileset:TTiled_Tileset
		For tileset = EachIn Self.tilesets
			tileset.Prepare()
		Next

		' layers'
		Local layer:TTiled_Layer
		Local tile:TTiled_TilesetTile

		For layer  = EachIn Self.layers
			For Local t:Int=0 to layer.tile.Length()-1
				layer.tile[t].tileset_id = Self.GetTilesetIDByGID(layer.tile[t].gid)
				If (layer.tile[t].tileset_id<>-1) Then				
					layer.tile[t].tilewidth 	= Self.tilesets.Get(layer.tile[t].tileset_id).tilewidth
					layer.tile[t].tileheight 	= Self.tilesets.Get(layer.tile[t].tileset_id).tileheight
					layer.tile[t].atlas			= Self.tilesets.Get(layer.tile[t].tileset_id).image.atlas					
					layer.tile[t].id  			= layer.tile[t].gid - Self.tilesets.Get(layer.tile[t].tileset_id).firstgid
					layer.tile[t].tileset_tile	= null
					
					' assign tile definition from tilesets to tile in layer
					For tile = EachIn self.tilesets.Get(layer.tile[t].tileset_id).tiles
						If tile.id = layer.tile[t].id Then 
							layer.tile[t].tileset_tile = tile
						EndIf
					Next
					
				End If
			Next
		Next

		' objects'
		Local objg:TTiled_ObjectGroup
		Local o:TTiled_ObjectGroupObject
		For objg = EachIn Self.object_groups
			For o = EachIn objg.objects
				o.tileset_id = Self.GetTilesetIDByGID(o.gid)
				If (o.tileset_id<>-1) Then				
					o.tilewidth 	= Self.tilesets.Get(o.tileset_id).tilewidth
					o.tileheight 	= Self.tilesets.Get(o.tileset_id).tileheight
					o.atlas			= Self.tilesets.Get(o.tileset_id).image.atlas
					o.id			= o.gid - Self.tilesets.Get(o.tileset_id).firstgid
				End If
			Next

		Next

		
	End Method
	
	'------------------------------------------------------------------------------------
	' Draw LAYER by ID
	'------------------------------------------------------------------------------------
	Method DrawLayer:Void(id:Int)

		Local layer:TTiled_Layer

		layer = Self.layers.Get(id)		
		
			Local bx% = Self.position.x
			Local by% = Self.position.y
			Local bw% = DISPLAY.VSCREEN_WIDTH
			Local bh% = DISPLAY.VSCREEN_HEIGHT
			Local x%, y%, rx%, ry%, mx%, my%, mx2%, my2%, modx%, mody%
			Local gid:Int
			Local sx:Float = layer.scale_x
			Local sy:Float = layer.scale_y
			
			If layer.moveable=False Then
				bx = 0
				by = 0
			End

			If Self.orientation =  PROP_MAP_ORTOGONAL Then
			
					modx = (bx * layer.parallax_scale_x) mod self.tilewidth
					mody = (by * layer.parallax_scale_y) mod self.tileheight
					y = by
					my = Int(Floor(Float(by * layer.parallax_scale_y) / Float(self.tileheight)))
					While y < by + bh + self.tileheight
						x = bx 
						mx = Int(Floor(Float(bx * layer.parallax_scale_x) / Float(self.tilewidth)))
						While x < bx + bw + self.tilewidth
							If (layer.wrap_x Or (mx >= 0 And mx < self.width)) And (layer.wrap_y Or (my >= 0 And my < self.height)) Then
								mx2 = mx
								my2 = my
								While mx2 < 0
									mx2 += self.width
								End
								While mx2 >= self.width
									mx2 -= self.width
								End
								While my2 < 0
									my2 += self.height
								End
								While my2 >= self.height
									my2 -= self.height
								End
								
								Local tile:TTiled_LayerTile = layer.GetTile(mx2,my2)
								gid = tile.id
								If gid > 0 Then
									If modx < 0 Then modx += Self.tilewidth
									If mody < 0 Then mody += Self.tileheight
									rx = (x - modx - bx)*layer.scale_x
									ry = (y - mody - by)*layer.scale_y		

									If tile.flip_y=1 and tile.flip_x=1 Then tile.atlas.DrawTile(rx,ry,gid,0,sx*tile.flip_x,sy*tile.flip_y)
									If tile.flip_y=-1 Then tile.atlas.DrawTile(rx,ry+tile.tileheight,gid,0,sx*tile.flip_x,sy*tile.flip_y)									
									If tile.flip_x=-1 Then tile.atlas.DrawTile(rx+tile.tilewidth,ry,gid,0,sx*tile.flip_x,sy*tile.flip_y)									
									If tile.flip_y=-1 And tile.flip_x=-1 Then tile.atlas.DrawTile(rx+tile.tilewidth,ry+tile.tileheight,gid,0,sx*tile.flip_x,sy*tile.flip_y)									
									
									
								End
							End
							x += self.tilewidth
							mx += 1
						End
						y += self.tileheight
						my += 1
					End	
			End

	End Method

	'------------------------------------------------------------------------------------
	' Draw LAYER by ID (use it only when you need show tyhis group )
	'------------------------------------------------------------------------------------
	Method DrawObjectGroup:Void(id:Int)

		Local objg:TTiled_ObjectGroup
		objg = Self.object_groups.Get(id)	

		For Local obj:TTiled_ObjectGroupObject = EachIn objg.objects
			obj.Draw(Self.position.x,Self.position.y)
		Next

	End Method

	
	Method DrawLayers:Void()
		For Local i:Int=0 to Self.layers.Size()-1
			if Self.layers.Get(i).visible Then Self.DrawLayer(i)
		Next
	End Method
End Class